package priv.pfz.sbv;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author yyjxp
 * 2024/2/16 2:52
 */
public class DependenciesGenerator {

    public static void generate(Visualizer visualizer) {
        //从beanFactory中获取bean依赖关系
        String[] beanNames = visualizer.getApplicationContext().getBeanDefinitionNames();
        BeanFactory beanFactory = visualizer.getApplicationContext().getAutowireCapableBeanFactory();
        Map<String, Set<String>> dependentBeanMap = getField(beanFactory, "dependentBeanMap");
        Map<String, Set<String>> dependenciesForBeanMap = getField(beanFactory, "dependenciesForBeanMap");

        //判断是否有环
        Map<String, Set<String>> cycleMap = getCycleMap(dependenciesForBeanMap);

        //bean依赖关系转换为json
        JSONArray categories = new JSONArray();
        for (BeanCategory beanCategory : visualizer.getBeanCategories()) {
            categories.add(beanCategory.getCategoryName());
        }
        JSONArray dataList = new JSONArray();
        for (String beanName : beanNames) {
            if (visualizer.isExcludeSpringInnerBean() && Utils.isSpringInnerBean(beanName)) {
                continue;
            }
            JSONObject data = new JSONObject()
                    .fluentPut("name", beanName)
                    .fluentPut("symbolSize", getSymbolSize(dependentBeanMap, beanName))
                    .fluentPut("category", getCategory(visualizer, beanName))
                    .fluentPut("draggable", true);
            dataList.add(data);
        }
        JSONArray links = new JSONArray();
        for (Map.Entry<String, Set<String>> entry : dependenciesForBeanMap.entrySet()) {
            String beanName = entry.getKey();
            for (String dependentBeanName : entry.getValue()) {
                JSONObject link = new JSONObject()
                        .fluentPut("source", dependentBeanName)
                        .fluentPut("target", beanName);
                if (cycleMap.get(dependentBeanName).contains(beanName)) {
                    link.fluentPut("lineStyle", new JSONObject().fluentPut("color", "red"));
                }
                links.add(link);
            }
        }

        //将json填充到模板中
        String imports = Utils.readResource(visualizer.isGenerateLocalHtml() ? "echarts.min.js" : "echarts_imports.html");
        Map<String, String> params = ImmutableMap.of(
                "imports", imports,
                "categories", categories.toJSONString(),
                "data", dataList.toJSONString(),
                "links", links.toJSONString()
        );
        String filename = Utils.getFilename(visualizer.getHtmlPath(), "dependencies.html");
        Utils.writeFile("dependencies_template.ftl", params, filename);
    }

    private static Map<String, Set<String>> getField(BeanFactory beanFactory, String fieldName) {
        try {
            Field field = DefaultSingletonBeanRegistry.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            return (Map<String, Set<String>>) field.get(beanFactory);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取循环依赖
     * key: beanName
     * value: 该bean处于的环的所有节点
     */
    private static Map<String, Set<String>> getCycleMap(Map<String, Set<String>> dependenciesForBeanMap) {
        DefaultDirectedGraph<String, DefaultEdge> graph = new DefaultDirectedGraph<>(DefaultEdge.class);
        for (Map.Entry<String, Set<String>> entry : dependenciesForBeanMap.entrySet()) {
            String beanName = entry.getKey();
            graph.addVertex(beanName);
            for (String dependentBeanName : entry.getValue()) {
                graph.addVertex(dependentBeanName);
                graph.addEdge(dependentBeanName, beanName);
            }
        }
        CycleDetector<String, DefaultEdge> cycleDetector = new CycleDetector<>(graph);
        Map<String, Set<String>> cycleMap = Maps.newHashMap();
        for (String vertex : graph.vertexSet()) {
            cycleMap.put(vertex, cycleDetector.findCyclesContainingVertex(vertex));
        }
        return cycleMap;
    }

    private static double getSymbolSize(Map<String, Set<String>> dependentBeanMap, String beanName) {
        return Math.sqrt(dependentBeanMap.containsKey(beanName) ? dependentBeanMap.get(beanName).size() : 1) * 20;
    }

    private static int getCategory(Visualizer visualizer, String beanName) {
        List<BeanCategory> beanCategories = visualizer.getBeanCategories();
        for (int i = 1; i < beanCategories.size(); i++) {
            BeanCategory beanCategory = beanCategories.get(i);
            if (beanCategory.match(beanName)) {
                return i;
            }
        }
        return 0;
    }
}
